﻿//-----------------------------------------------------------------------
// <copyright file="CommunicationManagerBase.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using FastNET.CleanAct.Common.EngineModel.Managers.Interface;

#endregion

namespace FastNET.CleanAct.Common.EngineModel.Managers
{
    /// <summary>
    /// Base class for all communication manager systems
    /// </summary>
    public abstract class CommunicationManagerBase : ICommunicationManager
    {
        #region Variables

        private readonly Dictionary<Type, ChannelFactory<IChannel>> _channelFactories;
        private readonly Dictionary<Type, ServiceHost> _serviceHosts;
        private readonly Dictionary<Type, IChannel> _channels;

        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the CommunicationManagerBase class
        /// </summary>
        protected CommunicationManagerBase()
        {
            _channelFactories = new Dictionary<Type, ChannelFactory<IChannel>>();
            _channels = new Dictionary<Type, IChannel>();
            _serviceHosts = new Dictionary<Type, ServiceHost>();
        }

        #endregion

        #region Public Connection Management Methods

        /// <summary>
        /// Opens all ChannelFactories and prepares the channels for use
        /// </summary>
        public void OpenFactories()
        {
            if (_channelFactories.Count <= 0)
            {
                return;
            }

            IEnumerable<Type> factoriesToRemove = (from factory in _channelFactories
                                                   where
                                                       factory.Value.State != CommunicationState.Opened &&
                                                       factory.Value.State != CommunicationState.Opening
                                                   select factory.Key);

            foreach (Type item in factoriesToRemove)
            {
                FinalizeFactory(item);
            }

            List<Type> factoriesToOpen = (from typeItem in GetChannelTypes() where !_channelFactories.ContainsKey(typeItem) select typeItem).ToList();

            foreach (Type item in factoriesToOpen)
            {
                ChannelFactory<IChannel> factory = InitializeFactory(item);

                if (item == null)
                {
                    continue;
                }

                _channelFactories.Add(item, factory);
            }

            // Open up channels as well
            OpenChannels();
        }

        /// <summary>
        /// Closes all ChannelFactories gracefully along with any active channels
        /// </summary>
        public void CloseFactories()
        {
            if (_channelFactories.Count <= 0)
            {
                return;
            }

            CloseChannels();

            IEnumerable<Type> factoriesToRemove = (from factory in _channelFactories select factory.Key);

            foreach (Type item in factoriesToRemove)
            {
                FinalizeFactory(item);
            }
        }

        /// <summary>
        /// Opens all channels for communication
        /// </summary>
        public void OpenChannels()
        {
            if (_channels.Count <= 0)
            {
                return;
            }

            IEnumerable<Type> channelsToRemove = (from channel in _channels
                                                  where
                                                      channel.Value.State != CommunicationState.Opened &&
                                                      channel.Value.State != CommunicationState.Opening
                                                  select channel.Key);

            foreach (Type item in channelsToRemove)
            {
                FinalizeChannel(item);
            }

            List<Type> channelsToOpen = (from typeItem in GetChannelTypes() where !_channels.ContainsKey(typeItem) select typeItem).ToList();

            foreach (Type item in channelsToOpen)
            {
                IChannel channel = InitializeChannel(item);

                if (item == null)
                {
                    continue;
                }

                _channels.Add(item, channel);
            }
        }

        /// <summary>
        /// Closes all channels actively in use
        /// </summary>
        public void CloseChannels()
        {
            if (_channels.Count <= 0)
            {
                return;
            }

            IEnumerable<Type> channelsToRemove = (from channel in _channels select channel.Key);

            foreach (Type item in channelsToRemove)
            {
                FinalizeChannel(item);
            }
        }

        /// <summary>
        /// Opens all hosts for communication
        /// </summary>
        public void OpenHosts()
        {
            if (_serviceHosts.Count <= 0)
            {
                return;
            }

            IEnumerable<Type> itemsToRemove = (from item in _serviceHosts
                                               where
                                                   item.Value.State != CommunicationState.Opened &&
                                                   item.Value.State != CommunicationState.Opening
                                               select item.Key);

            foreach (Type item in itemsToRemove)
            {
                FinalizeHost(item);
            }

            List<Type> itemsToOpen = (from typeItem in GetHostTypes() where !_serviceHosts.ContainsKey(typeItem) select typeItem).ToList();

            foreach (Type item in itemsToOpen)
            {
                ServiceHost host = InitializeHost(item);

                if (item == null)
                {
                    continue;
                }

                _serviceHosts.Add(item, host);
            }
        }

        /// <summary>
        /// Closes all hosts actively in use
        /// </summary>
        public void CloseHosts()
        {
            if (_serviceHosts.Count <= 0)
            {
                return;
            }

            IEnumerable<Type> itemsToRemove = (from item in _serviceHosts select item.Key);

            foreach (Type item in itemsToRemove)
            {
                FinalizeHost(item);
            }
        }

        #endregion

        #region Channel Retrieval Methods

        /// <summary>
        /// Retrieves the instance of the channel factory to control
        /// </summary>
        /// <typeparam name="TFactory">Type of the ChannelFactory</typeparam>
        /// <typeparam name="TChannel">Type of the Channel</typeparam>
        /// <returns>ChannelFactory found or null if no factory is available</returns>
        protected TFactory RetrieveChannelFactory<TFactory, TChannel>() where TFactory
            : ChannelFactory<TChannel>
        {
            if (_channelFactories.ContainsKey(typeof(TChannel)))
            {
                return _channelFactories[typeof(TChannel)] as TFactory;
            }

            return null;
        }

        /// <summary>
        /// Retrieves the instance of the channel to control
        /// </summary>
        /// <typeparam name="TChannel">Type of the Channel</typeparam>
        /// <returns>Channel found or null if no channel is available</returns>
        protected TChannel RetrieveChannel<TChannel>()
        {
            if (_channels.ContainsKey(typeof(TChannel)))
            {
                return (TChannel)_channels[typeof(TChannel)];
            }

            return default(TChannel);
        }

        /// <summary>
        /// Retrieves the instance of the host to control
        /// </summary>
        /// <typeparam name="THost">Type of the Host</typeparam>
        /// <returns>Host found or null if no channel is available</returns>
        protected THost RetrieveHost<THost>() where THost : ServiceHost
        {
            if (_serviceHosts.ContainsKey(typeof(THost)))
            {
                return _serviceHosts[typeof(THost)] as THost;
            }

            return null;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Called to allow the inherited class to initialize the instance of the factory for the requested channel type
        /// </summary>
        /// <param name="channelType">Type of the channel the factory should be for</param>
        /// <returns>ChannelFactory of IChannel that was created</returns>
        protected abstract ChannelFactory CreateFactoryInstance(Type channelType);

        /// <summary>
        /// Called to allow the inherited class to initialize the instance of the ServiceHost for the requested host type
        /// </summary>
        /// <param name="hostType">Type of the host should be for</param>
        /// <returns>ServiceHost that was created</returns>
        protected abstract ServiceHost CreateHostInstance(Type hostType);

        /// <summary>
        /// Gets a list of all channel types this communication manager is managing
        /// </summary>
        /// <returns>List of Type for all supported channels</returns>
        protected abstract List<Type> GetChannelTypes();

        /// <summary>
        /// Gets a list of all host types this communication manager is managing
        /// </summary>
        /// <returns>List of Type for all supported host</returns>
        protected abstract List<Type> GetHostTypes();

        #endregion

        #region Private Static WCF Events

        private static void ChannelFactory_Closed(object sender, EventArgs e)
        {
        }

        private static void ChannelFactory_Faulted(object sender, EventArgs e)
        {
        }

        private static void ChannelFactory_Opened(object sender, EventArgs e)
        {
        }

        private static void Channel_Opened(object sender, EventArgs e)
        {
        }

        private static void Channel_Faulted(object sender, EventArgs e)
        {
        }

        private static void Channel_Closed(object sender, EventArgs e)
        {
        }

        private static void Host_Opened(object sender, EventArgs e)
        {
        }

        private static void Host_Faulted(object sender, EventArgs e)
        {
        }

        private static void Host_Closed(object sender, EventArgs e)
        {
        }

        #endregion

        #region Private System Management Methods

        /// <summary>
        /// Initializes the Channel for the given channel type
        /// </summary>
        /// <param name="channelType">Type of the channel to initialize</param>
        /// <returns>IChannel that was created</returns>
        private IChannel InitializeChannel(Type channelType)
        {
            IChannel channel = null;

            if (_channelFactories.ContainsKey(channelType))
            {
                channel = _channelFactories[channelType].CreateChannel();
            }

            if (channel == null)
            {
                return null;
            }

            channel.Closed += Channel_Closed;
            channel.Faulted += Channel_Faulted;
            channel.Opened += Channel_Opened;

            channel.Open();

            return channel;
        }

        /// <summary>
        /// Initializes the ChannelFactory for the given channel type
        /// </summary>
        /// <param name="channelType">Type of the channel to initialize</param>
        /// <returns>ChannelFactory of IChannel that was created</returns>
        private ChannelFactory<IChannel> InitializeFactory(Type channelType)
        {
            ChannelFactory<IChannel> factory = (ChannelFactory<IChannel>)CreateFactoryInstance(channelType);

            if (factory == null)
            {
                return null;
            }

            factory.Closed += ChannelFactory_Closed;
            factory.Faulted += ChannelFactory_Faulted;
            factory.Opened += ChannelFactory_Opened;

            factory.Open();

            return factory;
        }

        /// <summary>
        /// Initializes the ServiceHost for the given host type
        /// </summary>
        /// <param name="hostType">Type of the channel to initialize</param>
        /// <returns>ServiceHost that was created</returns>
        private ServiceHost InitializeHost(Type hostType)
        {
            ServiceHost host = CreateHostInstance(hostType);

            if (host == null)
            {
                return null;
            }

            host.Closed += Host_Closed;
            host.Faulted += Host_Faulted;
            host.Opened += Host_Opened;

            host.Open();

            return host;
        }

        /// <summary>
        /// Finalizes the Channel for the given channel type
        /// </summary>
        /// <param name="channelType">Type of the channel to finalize</param>
        private void FinalizeChannel(Type channelType)
        {
            IChannel channel = _channels[channelType];

            _channels.Remove(channelType);

            //Are we already open and working?
            if (channel.State == CommunicationState.Opened ||
                channel.State == CommunicationState.Opening)
            {
                channel.Close();
            }

            channel.Closed -= Channel_Closed;
            channel.Faulted -= Channel_Faulted;
            channel.Opened -= Channel_Opened;
        }

        /// <summary>
        /// Finalizes the ChannelFactory for the given channel type
        /// </summary>
        /// <param name="channelType">Type of the channel to finalize</param>
        private void FinalizeFactory(Type channelType)
        {
            IChannelFactory factory = _channelFactories[channelType];

            _channelFactories.Remove(channelType);

            //Are we already open and working?
            if (factory.State == CommunicationState.Opened ||
                factory.State == CommunicationState.Opening)
            {
                factory.Close();
            }

            factory.Opened -= ChannelFactory_Opened;
            factory.Faulted -= ChannelFactory_Faulted;
            factory.Closed -= ChannelFactory_Closed;
        }

        /// <summary>
        /// Finalizes the ServiceHost for the given host type
        /// </summary>
        /// <param name="hostType">Type of the ServiceHost to finalize</param>
        private void FinalizeHost(Type hostType)
        {
            ServiceHost host = _serviceHosts[hostType];

            _channelFactories.Remove(hostType);

            //Are we already open and working?
            if (host.State == CommunicationState.Opened ||
                host.State == CommunicationState.Opening)
            {
                host.Close();
            }

            host.Opened -= Host_Opened;
            host.Faulted -= Host_Faulted;
            host.Closed -= Host_Closed;
        }

        #endregion
    }
}